#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import pwd
import stat
import time

from common.cleaner import clear
from common.common import invoke_rpc_tool_interface, execute_cmd, is_clone_file_system
from common.common_models import SubJobDetails
from common.const import SubJobStatusEnum, CopyDataTypeEnum, RpcToolInterface
from common.exception.common_exception import ErrCodeException
from common.file_common import change_path_permission
from common.job_const import ParamKeyConst
from common.number_const import NumberConst
from common.parse_parafile import ParamFileUtil, CopyParamParseUtil
from common.util.check_utils import check_repo_path_raise_ex
from common.util.cmd_utils import cmd_format
from common.util.common_utils import get_group_name_by_os_user
from common.util.exec_utils import su_exec_cat_cmd
from gaussdbt.single import LOGGER
from gaussdbt.single.comm.gst_single_const import SingleRestoreParam, GstSingleConst, GstSingleKeyConst
from gaussdbt.single.comm.gst_single_error_code import ErrorCode
from gaussdbt.single.comm.utils.gst_single_backup_utils import GstSingleBackupUtil
from gaussdbt.single.comm.utils.gst_single_comm_utils import GstSingleCommonUtil


class SingleRestoreService:
    def __init__(self, pid, job_id, sub_job_id, job_param, os_user):
        self.pid = pid
        self.job_id = job_id
        self.sub_job_id = sub_job_id
        self.job_param = job_param
        self.os_user = os_user
        self.job_status = SubJobStatusEnum.RUNNING
        self.job_datails = list()

    def is_restore_time_matched(self):
        LOGGER.info('Start check restore timestamp is matched.')
        restore_timestamp = int(self.job_param.get(ParamKeyConst.JOB, {}).get(ParamKeyConst.EXT_INFO,
            {}).get(ParamKeyConst.RESTORE_TIMESTAMP))
        # 指定的时间不得晚于当前时间
        current_timestamp = int(time.time())
        if restore_timestamp > current_timestamp:
            LOGGER.error('Restore time is later than current time, jobId=%s', self.job_id)
            return False
        # 指定的时间不得早于最后一次全量备份集的备份完成时间
        last_full_copy_timestamp = 0
        for copy in CopyParamParseUtil.get_copies(self.job_param):
            copy_type = CopyParamParseUtil.get_copy_type(copy)
            if copy_type == CopyDataTypeEnum.FULL_COPY:
                last_full_copy_timestamp = int(copy.get(ParamKeyConst.EXT_INFO, {}).get(ParamKeyConst.COPY_BAK_TIME))
                break
        if restore_timestamp < last_full_copy_timestamp:
            LOGGER.error('Restore time is earlier than last full copy time, jobId=%s', self.job_id)
            return False
        return True

    def is_version_matched(self):
        LOGGER.info('Start check restore version is matched.')
        current_version = self.job_param.get(ParamKeyConst.JOB, {}).get(ParamKeyConst.PROTECT_OBJECT,
            {}).get(ParamKeyConst.EXT_INFO, {}).get("version")
        restore_version = None
        for copy in CopyParamParseUtil.get_copies(self.job_param):
            copy_type = CopyParamParseUtil.get_copy_type(copy)
            if copy_type == CopyDataTypeEnum.FULL_COPY:
                restore_version = copy.get(ParamKeyConst.EXT_INFO, {}).get("copyProtectObjectVersion")
                break
        if not restore_version == current_version:
            LOGGER.error('Restore version does not match the current version, '
                'restore version: %s, current version: %s', restore_version, current_version)
            return False
        return True

    def report_job_completed(self):
        job_details = SubJobDetails(taskId=self.job_id, subTaskId=self.sub_job_id,
            taskStatus=SubJobStatusEnum.COMPLETED, progress=NumberConst.HUNDRED).dict(by_alias=True)
        try:
            invoke_rpc_tool_interface(f'{self.job_id}_{self.sub_job_id}',
                RpcToolInterface.REPORT_JOB_DETAIL, job_details)
        except Exception as err:
            LOGGER.exception('Report job cpmpleted failed, jobId=%s, subJobId=%s, jobStatus=%d',
                self.job_id, self.sub_job_id, SubJobStatusEnum.COMPLETED)

    def exec_full_restore(self, restore_param: SingleRestoreParam):
        """全量副本恢复"""
        LOGGER.info('Start exec restore cmd jobId=%s, subJobId=%s', self.job_id, self.sub_job_id)
        # 新位置恢复，修改副本属主及属组
        check_repo_path_raise_ex(restore_param.full_copy_path)
        if is_clone_file_system(self.job_param):
            if self.is_restore_new_loaction():
                group_name = get_group_name_by_os_user(self.os_user)
                execute_cmd(cmd_format("chown -hR {}:{} {}", self.os_user, group_name, restore_param.full_copy_path))
            for file_name in os.listdir(f'{restore_param.full_copy_path}'):
                mode = stat.S_IRUSR | stat.S_IWUSR
                change_path_permission(f'{restore_param.full_copy_path}/{file_name}', mode=mode)
        enable_login, sys_pwd = GstSingleBackupUtil.get_enable_sysdba_login_info(self.pid, self.job_param, True)
        try:
            if enable_login:
                cmd = cmd_format("python3 {} -t full-recover -D {} -B {} -A {} -R {}", restore_param.zbackup_file,
                                 restore_param.gsdb_data, restore_param.full_copy_path, restore_param.gsdb_home,
                                 restore_param.instance_role)
                GstSingleBackupUtil.execute_instance_backup(cmd, self.os_user)
            else:
                cmd = cmd_format("python3 {} -t full-recover -D {} -B {} -A {} -R {} -P", restore_param.zbackup_file,
                                 restore_param.gsdb_data, restore_param.full_copy_path, restore_param.gsdb_home,
                                 restore_param.instance_role)
                GstSingleBackupUtil.execute_instance_backup_with_pwd(cmd, self.os_user, sys_pwd)
        finally:
            clear(sys_pwd)

    def exec_log_restore(self, restore_param: SingleRestoreParam):
        """时间点恢复"""
        # 新位置恢复，修改副本属主及属组
        check_repo_path_raise_ex(restore_param.full_copy_path)
        check_repo_path_raise_ex(restore_param.archive_log_copy_path)
        if is_clone_file_system(self.job_param):
            if self.is_restore_new_loaction():
                group_name = get_group_name_by_os_user(self.os_user)
                execute_cmd(cmd_format("chown -hR {}:{} {}", self.os_user, group_name, restore_param.full_copy_path))
                execute_cmd(
                    cmd_format("chown -hR {}:{} {}", self.os_user, group_name, restore_param.archive_log_copy_path))
            for file_name in os.listdir(f'{restore_param.full_copy_path}'):
                mode = stat.S_IRUSR | stat.S_IWUSR
                change_path_permission(f'{restore_param.full_copy_path}/{file_name}', mode=mode)
            for file_name in os.listdir(f'{restore_param.archive_log_copy_path}'):
                mode = stat.S_IRUSR | stat.S_IWUSR
                change_path_permission(f'{restore_param.archive_log_copy_path}/{file_name}', mode=mode)
        enable_login, sys_pwd = GstSingleBackupUtil.get_enable_sysdba_login_info(self.pid, self.job_param, True)
        try:
            if enable_login:
                cmd = cmd_format("python3 {} -t archivelog-recover -D {} -B {} -I {} -A {} -R {}",
                                 restore_param.zbackup_file, restore_param.gsdb_data, restore_param.full_copy_path,
                                 restore_param.archive_log_copy_path, restore_param.gsdb_home,
                                 restore_param.instance_role)
                cmd = f"{cmd} -E \"{restore_param.restore_time}\""
                GstSingleBackupUtil.execute_instance_backup(cmd, self.os_user)
            else:
                cmd = cmd_format("python3 {} -t archivelog-recover -D {} -B {} -I {} -A {} -R {} -P",
                                 restore_param.zbackup_file, restore_param.gsdb_data, restore_param.full_copy_path,
                                 restore_param.archive_log_copy_path,
                                 restore_param.gsdb_home, restore_param.instance_role)
                cmd = f"{cmd} -E \"{restore_param.restore_time}\""
                GstSingleBackupUtil.execute_instance_backup_with_pwd(cmd, self.os_user, sys_pwd)
        finally:
            clear(sys_pwd)

    def get_full_restore_param(self):
        """获取全量副本恢复参数"""
        LOGGER.info('Start get full copy restore param jobId=%s, subJobId=%s', self.job_id, self.sub_job_id)
        gsdb_home = GstSingleCommonUtil.get_val_of_gsdb_env_var(self.os_user, "GSDB_HOME")
        gsdb_data = GstSingleCommonUtil.get_val_of_gsdb_env_var(self.os_user, "GSDB_DATA")
        zbackup_file = os.path.realpath(os.path.join(gsdb_home, "bin", "zbackup.py"))
        GstSingleCommonUtil.check_params(
            check_users=[self.os_user], check_paths=[zbackup_file, gsdb_data],
            check_path_owners=[(zbackup_file, [self.os_user])])
        ret, full_copy_path = self.get_restore_data_copy_path()
        restore_param = SingleRestoreParam(zbackup_file=zbackup_file, gsdb_data=gsdb_data,
            full_copy_path=full_copy_path, gsdb_home=gsdb_home)
        restore_param.os_user = self.os_user
        return True, restore_param

    def get_log_restore_param(self):
        """获取时间点恢复参数"""
        LOGGER.info('Start get log restore param jobId=%s, subJobId=%s', self.job_id, self.sub_job_id)
        ret, restore_param = self.get_full_restore_param()
        ret, log_copy_path = self.get_restore_log_copy_path()
        restore_param.archive_log_copy_path = log_copy_path
        restore_timestamp = int(self.job_param.get(ParamKeyConst.JOB, {}).get(ParamKeyConst.EXT_INFO,
            {}).get(ParamKeyConst.RESTORE_TIMESTAMP))
        restore_format_timestamp = time.strftime(GstSingleConst.GSDB_TIME_FORMAT, time.localtime(restore_timestamp))
        restore_param.restore_time = GstSingleCommonUtil.convert_time_to_utc_time(restore_format_timestamp)
        LOGGER.info('Rstore time is: %s', restore_param.restore_time)
        return True, restore_param

    def get_restore_data_copy_path(self):
        LOGGER.info('Start get restore data copy path, jobId=%s, subJobId=%s', self.job_id, self.sub_job_id)
        """获取恢复全量副本路径"""
        data_repo_path_list = []
        repo_path = dict()
        full_copy_dir = ""
        full_copy_paths = []
        for copy in CopyParamParseUtil.get_copies(self.job_param):
            copy_type = CopyParamParseUtil.get_copy_type(copy)
            if copy_type == CopyDataTypeEnum.FULL_COPY:
                data_repo = CopyParamParseUtil.get_data_reps(copy)
                repo_path = ParamFileUtil.parse_backup_path(data_repo)
                full_copy_dir = copy.get(ParamKeyConst.EXT_INFO, {}).get(GstSingleKeyConst.BAK_DIR_NAME)
                break
            if copy_type in (CopyDataTypeEnum.S3_ARCHIVE, CopyDataTypeEnum.TAP_ARCHIVE):
                data_repo = CopyParamParseUtil.get_data_reps(copy)
                repo_path = ParamFileUtil.parse_backup_path(data_repo)
                full_copy_dir = copy.get(ParamKeyConst.EXT_INFO, {}).get(ParamKeyConst.EXT_INFO,
                    {}).get(GstSingleKeyConst.BAK_DIR_NAME)
                break
        data_repo_path_list = repo_path.get("data_repository", [])
        if not data_repo_path_list:
            LOGGER.error('Full copy path list is empty, jobId=%s, subJobId=%s', self.job_id, self.sub_job_id)
            raise ErrCodeException(ErrorCode.PARAMS_IS_INVALID, "Full copy path list is empty.")
        full_copy_paths.append(os.path.join(data_repo_path_list[0], full_copy_dir))
        LOGGER.info('Get restore data copy path success, path=[%s], jobId=%s, subJobId=%s',
            full_copy_paths[0], self.job_id, self.sub_job_id)
        return True, full_copy_paths[0]

    def get_restore_log_copy_path(self):
        """获取时间点恢复日志副本路径"""
        LOGGER.info('Start get restore log copy path, jobId=%s, subJobId=%s', self.job_id, self.sub_job_id)
        log_repo_path = []
        full_copy_id = ""
        repo_path = dict()
        for copy in CopyParamParseUtil.get_copies(self.job_param):
            copy_type = CopyParamParseUtil.get_copy_type(copy)
            if copy_type == CopyDataTypeEnum.LOG_COPY:
                log_repo = CopyParamParseUtil.get_log_reps(copy)
                repo_path = ParamFileUtil.parse_backup_path(log_repo)
            if copy_type == CopyDataTypeEnum.FULL_COPY:
                full_copy_id = CopyParamParseUtil.get_copy_id(copy)
        log_repo_path = repo_path.get("log_repository", [])
        restore_timestamp = int(self.job_param.get(ParamKeyConst.JOB, {}).get(ParamKeyConst.EXT_INFO,
            {}).get(ParamKeyConst.RESTORE_TIMESTAMP))
        restore_copy_meta_path = os.path.realpath(os.path.join(os.path.dirname(log_repo_path[0]),
                                                               f"{full_copy_id}.meta"))
        # 获取文件的元数据信息
        stat_info = os.stat(restore_copy_meta_path)
        # 获取属主用户的用户名
        owner_username = pwd.getpwuid(stat_info.st_uid).pw_name
        ret_code, out = su_exec_cat_cmd(restore_copy_meta_path, owner_username)
        if not ret_code:
            LOGGER.error('Get log repo meta file info failed, file: %s, code: %s, out: %s',
                         restore_copy_meta_path, ret_code, out)
            raise ErrCodeException(ErrorCode.PARAMS_IS_INVALID, "Get log repo meta file info failed.")
        # meta信息格式：copyId;beginTimestamp~endTimestamp;
        log_copy_dir = ""
        meta_info = out.strip()
        lines = meta_info.split('\n')
        for line in lines:
            timestamp_range = line.split(';')[1]
            begin_timestamp = int(timestamp_range.split('~')[0])
            end_timestamp = int(timestamp_range.split('~')[1])
            if restore_timestamp >= begin_timestamp and restore_timestamp <= end_timestamp:
                log_copy_dir = line.split(';')[0]
                break
        log_cpoy_path = os.path.realpath(os.path.join(os.path.dirname(log_repo_path[0]), log_copy_dir))
        LOGGER.info('Get restore log copy path success, path=[%s], jobId=%s, subJobId=%s',
            log_cpoy_path, self.job_id, self.sub_job_id)
        return True, log_cpoy_path

    def report_progress(self):
        LOGGER.info(f'Start report progress thread. jobId=%s, subIobId=%s', self.job_id, self.sub_job_id)
        while True:
            is_complete = (self.job_status != SubJobStatusEnum.RUNNING)
            if len(self.job_datails) != 0:
                job_details = SubJobDetails(taskId=self.job_id, subTaskId=self.sub_job_id, taskStatus=self.job_status,
                    logDetail=self.job_datails, progress=NumberConst.ZERO).dict(by_alias=True)
                self.job_datails.clear()
            else:
                job_details = SubJobDetails(taskId=self.job_id, subTaskId=self.sub_job_id, taskStatus=self.job_status,
                    progress=NumberConst.ZERO).dict(by_alias=True)
            try:
                invoke_rpc_tool_interface(self.job_id, RpcToolInterface.REPORT_JOB_DETAIL, job_details)
            except Exception as err:
                LOGGER.exception('Report job details failed, jobId=%s, subJobId=%s, jobStatus=%d', self.job_id,
                    self.sub_job_id, self.job_status)
            if is_complete:
                LOGGER.info(f'Stop report progress thread. jobId=%s, subIobId=%s, jobStatus=%d', self.job_id,
                    self.sub_job_id, self.job_status)
                break
            time.sleep(NumberConst.TWENTY)

    def is_restore_new_loaction(self):
        """是否新位置恢复"""
        target_location = self.job_param.get(ParamKeyConst.JOB).get(ParamKeyConst.EXT_INFO,
            {}).get(ParamKeyConst.RESTORE_TARGET_LOCATION)
        if target_location == "new":
            LOGGER.info('Restore job type is new loacltion, jobId=%s, subIobId=%s', self.job_id, self.sub_job_id)
            return True
        return False